package com.api.client.impl;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.api.client.model.ApiRequest;
import com.api.client.model.ApiRequestFactory;
import com.api.client.model.ApiServiceMetadata;
import com.api.request.model.RequestParameters;
import com.zyncro.api.model.IApiType;

public class ApiRequestFactoryImpl implements ApiRequestFactory {

	private Log logger = LogFactory.getLog(getClass());

	private final OAuthProvider provider;

	private final String host;

	public ApiRequestFactoryImpl(OAuthProvider provider, String host) {

		if (provider == null || host == null) {
			throw new IllegalArgumentException("LoginHandler, OauthProvider and host cannot be null");
		}
		this.provider = provider;
		this.host = host;
	}

	public ApiRequest getRequest(RequestParameters requestParameters) {

		ApiServiceMetadata metadata = requestParameters.getMetadata();
		Map<String, String> headers = requestParameters.getHeaders();
		Map<String, Object> pathParams = requestParameters.getPathParams();
		Map<String, Object> queryParams = requestParameters.getQueryParams();
		Map<String, Object> formParams = requestParameters.getFormParams();

		String accessKey = requestParameters.getAccessKey();
		String accessSecret = requestParameters.getAccessSecret();

		String url = host + replacePathParameters(metadata.getUrl(), pathParams);

		if (pathUrlNotComplete(url)) {
			logger.warn(String.format("Service url path could be not complete: '%s'", url));
		}

		ApiRequest request = null;

		if (!metadata.isMultipart()) {

			ApiScribeRequest scribeRequest = new ApiScribeRequest(metadata.getMethod(), url);
			scribeRequest.addHeaderValues(headers);
			scribeRequest.addQueryValues(getStringParams(queryParams));
			scribeRequest.addFormValues(getStringParams(formParams));

			if (accessKey != null && accessSecret != null) {
				scribeRequest.sign(provider, accessKey, accessSecret);
			} else {
				logger.warn("Not signing request because user is not logged in");
			}
			request = scribeRequest;
		} else {
			ApiMultipartRequest multipartRequest = new ApiMultipartRequest(metadata.getMethod(), url);
			multipartRequest.addHeaderValues(headers);
			multipartRequest.addQueryValues(getStringParams(queryParams));
			multipartRequest.addTextFormValues(getTextFormValues(formParams));
			multipartRequest.addFileFormValues(getFileFormValues(formParams));

			request = multipartRequest;
		}
		return request;
	}

	private String replacePathParameters(String baseUrl, Map<String, Object> pathParams) {

		String completeUrl = baseUrl;

		for (Map.Entry<String, Object> entry : pathParams.entrySet()) {

			String param = entry.getKey();

			if (entry.getValue() != null) {

				completeUrl = completeUrl.replace("{" + param + "}", getParameterValue(entry.getValue()));

			} else {
				throw new IllegalStateException(String.format("Path parameter '%s' cannot be null", param));
			}
		}
		return completeUrl;
	}

	private boolean pathUrlNotComplete(String url) {

		return url.contains("{") || url.contains("}");
	}

	private Map<String, String> getStringParams(Map<String, Object> objParams) {

		Map<String, String> map = new HashMap<String, String>();

		for (Map.Entry<String, Object> entry : objParams.entrySet()) {

			if (entry.getValue() != null) {

				map.put(entry.getKey(), getParameterValue(entry.getValue()));
			}
		}
		return map;
	}

	private String getParameterValue(Object object) {

		String value = null;

		if (List.class.isAssignableFrom(object.getClass())) {

			List<?> list = (List<?>) object;

			StringBuilder stringBuilder = new StringBuilder();

			for (Object element : list) {
				String elementValue = getValueOf(element);
				stringBuilder.append(elementValue);
				stringBuilder.append(",");
			}
			if (stringBuilder.length() > 0) {
				stringBuilder.deleteCharAt(stringBuilder.length() - 1);
			}
			value = stringBuilder.toString();

		} else {
			value = getValueOf(object);
		}
		return value;
	}

	private String getValueOf(Object object) {

		String value = null;

		if (IApiType.class.isAssignableFrom(object.getClass())) {

			IApiType apiType = (IApiType) object;

			value = String.valueOf(apiType.getId());

		} else {
			value = String.valueOf(object);
		}
		return value;
	}

	private Map<String, String> getTextFormValues(Map<String, Object> objParams) {

		Map<String, String> map = new HashMap<String, String>();

		for (Map.Entry<String, Object> entry : objParams.entrySet()) {

			if (entry.getValue() != null) {

				Class<?> valueClass = entry.getValue().getClass();

				if (!InputStream.class.isAssignableFrom(valueClass)) {

					map.put(entry.getKey(), getParameterValue(entry.getValue()));
				}
			}
		}
		return map;
	}

	private Map<String, FileInputStream> getFileFormValues(Map<String, Object> objParams) {

		Map<String, FileInputStream> map = new HashMap<String, FileInputStream>();

		for (Map.Entry<String, Object> entry : objParams.entrySet()) {

			if (entry.getValue() != null) {

				Class<?> valueClass = entry.getValue().getClass();

				if (FileInputStream.class.isAssignableFrom(valueClass)) {

					FileInputStream file = (FileInputStream) entry.getValue();

					map.put(entry.getKey(), file);
				}
			}
		}
		return map;
	}
}
